<script lang="ts" setup>
import { UploadResultStatus } from '#/components/ImageUpload/types/typing'
import { useMessage } from '#/hooks/useMessage'
import { IconifyIcon } from '@vben/icons'
import { $t } from '@vben/locales'
import { isFunction, isObject, isString } from '@vben/utils'
import type { UploadFile, UploadProps } from 'ant-design-vue'
import { Modal, Upload } from 'ant-design-vue'
import { ref, toRefs, warn, watch } from 'vue'
import VueDraggable from 'vuedraggable'
import { checkFileType } from './helper'
import { useUploadType } from './hooks/useUpload'
import { uploadContainerProps } from './props'

defineOptions({ name: 'ImageUpload' })

const props = defineProps({
    ...uploadContainerProps
})

const emit = defineEmits(['change', 'update:value', 'delete', 'beforeUpload'])

const model = defineModel()

const { createMessage } = useMessage()
const { accept, helpText, maxNumber, maxSize } = toRefs(props)
const isInnerOperate = ref<boolean>(false)
const { getStringAccept } = useUploadType({
    acceptRef: accept,
    helpTextRef: helpText,
    maxNumberRef: maxNumber,
    maxSizeRef: maxSize
})
const previewOpen = ref<boolean>(false)
const previewImage = ref<string>('')
const previewTitle = ref<string>('')

const fileList = ref<UploadProps['fileList']>([])
const isLtMsg = ref<boolean>(true)
const isActMsg = ref<boolean>(true)

watch(
    () => props.value,
    (v) => {
        if (isInnerOperate.value) {
            isInnerOperate.value = false
            return
        }
        if (v) {
            let value: string[] = []
            if (Array.isArray(v)) {
                value = v
            } else {
                value.push(v)
            }
            fileList.value = value.map((item, i) => {
                if (item && isString(item)) {
                    return {
                        uid: `${-i}`,
                        name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
                        status: 'done',
                        url: item
                    }
                } else if (item && isObject(item)) {
                    return item
                }
                return item
            }) as UploadProps['fileList']
        }
    }
)

function getBase64<T extends ArrayBuffer | null | string>(file: File) {
    return new Promise<T>((resolve, reject) => {
        const reader = new FileReader()
        reader.readAsDataURL(file)
        reader.addEventListener('load', () => {
            resolve(reader.result as T)
        })
        // eslint-disable-next-line unicorn/prefer-add-event-listener
        reader.onerror = (error) => reject(error)
    })
}

const handlePreview = async (file: UploadFile) => {
    if (!file.url && !file.preview) {
        file.preview = await getBase64<string>(file.originFileObj!)
    }
    previewImage.value = file.url || file.preview || ''
    previewOpen.value = true
    previewTitle.value =
        file.name || previewImage.value.slice(Math.max(0, previewImage.value.lastIndexOf('/') + 1))
}

function emitChange(value: any) {
    emit('change', value)
    model.value = value
}

const handleRemove = async (file: UploadFile) => {
    if (fileList.value) {
        const index = fileList.value.findIndex((item) => item.uid === file.uid)
        index !== -1 && fileList.value.splice(index, 1)
        const value = getValue()
        isInnerOperate.value = true

        emitChange(value)
        emit('delete', file)
    }
}

const handleCancel = () => {
    previewOpen.value = false
    previewTitle.value = ''
}
interface UploadFiles extends File {
    url?: string // 添加可选的 url 属性
}
const beforeUpload = (file: UploadFiles) => {
    const { maxSize, accept } = props

    const isAct = checkFileType(file, accept)
    if (!isAct) {
        createMessage.error($t('components.ImageUpload.children.uploadLimitAccept', [accept]))
        isActMsg.value = false
        // 防止弹出多个错误提示
        setTimeout(() => (isActMsg.value = true), 1000)
    }
    const isLt = file.size / 1024 / 1024 > maxSize
    if (isLt) {
        createMessage.error($t('components.ImageUpload.children.uploadLimit', [maxSize]))
        isLtMsg.value = false
        // 防止弹出多个错误提示
        setTimeout(() => (isLtMsg.value = true), 1000)
    }
    if (file.type.startsWith('video/')) {
        file.url = URL.createObjectURL(file)
    }

    const flag = (isAct && !isLt) || Upload.LIST_IGNORE

    if (props.isAutoUpload) {
        return flag
    }

    if (flag) {
        emit('beforeUpload', file)
    }

    return false
}

async function customRequest(info: any) {
            emitChange(info.file)
}

function getValue() {
    const list = (fileList.value || [])
        .filter((item) => item?.status === UploadResultStatus.DONE)
        .map((item: any) => {
            return item?.url || item?.response?.url
        })
    return props.multiple ? list : list.length > 0 ? list[0] : ''
}

function handleDragEnd() {
    const value = getValue()
    emitChange(value)
}
</script>

<template>
    <div>
        <!-- 多图展示拖拽 -->
        <VueDraggable
            v-if="multiple && draggable"
            v-model="fileList"
            :animation="200"
            :class="[{ offset: fileList && fileList.length > 0 }]"
            class="draggable-list"
            item-key="key"
            @dragend="handleDragEnd"
        >
            <template #item="{ element }">
                <div class="draggable-item">
                    <!-- 图片预览 -->
                    <img
                        v-if="!element?.url?.includes('.mp4')"
                        :src="element.url || element.thumbUrl"
                        alt="uploaded image"
                    />
                    <video v-else :src="element.url" style="width: 100%"></video>
                    <div class="actions">
                        <IconifyIcon
                            class="preview-icon"
                            icon="ant-design:eye"
                            @click="handlePreview(element)"
                        />
                        <IconifyIcon
                            class="delete-icon"
                            icon="ant-design:delete"
                            @click="handleRemove(element)"
                        />
                    </div>
                </div>
            </template>
        </VueDraggable>
        <Upload
            v-bind="$attrs"
            v-model:file-list="fileList"
            :accept="getStringAccept"
            :before-upload="beforeUpload"
            :custom-request="customRequest"
            :list-type="listType"
            :max-count="maxNumber"
            :multiple="multiple"
            @preview="handlePreview"
            @remove="handleRemove"
        >
            <template #default>
                <div v-if="fileList && fileList.length < maxNumber" class="text-center">
                    <IconifyIcon class="inline-block" icon="ant-design:plus-outlined" />
                    <div style="margin-top: 8px">
                        {{ $t('components.ImageUpload.children.upload') }}
                    </div>
                </div>
            </template>
        </Upload>
        <div v-if="helpText" style="font-size: 12px; color: #666">
            {{ helpText }}
        </div>
        <Modal :footer="null" :open="previewOpen" :title="previewTitle" @cancel="handleCancel">
            <img
                v-if="getStringAccept !== '.mp4' && !previewImage?.includes('.mp4')"
                :src="previewImage"
                alt=""
                style="width: 100%"
            />
            <video
                v-else
                :autoplay="true"
                :controls="true"
                :src="previewImage"
                style="width: 100%"
            ></video>
        </Modal>
    </div>
</template>

<style lang="less">
.ant-upload-select-picture-card i {
    color: #999;
    font-size: 32px;
}

.ant-upload-select-picture-card .ant-upload-text {
    margin-top: 8px;
    color: #666;
}
/deep/.ant-upload-list-item-container {
    display: none;
}
.draggable-list {
    display: flex;
    gap: 12px;
    &.offset {
        margin-bottom: -100px;
    }
}
.draggable-item {
    position: relative;
    width: 100px;
    height: 100px;
    border: 1px solid #d9d9d9;
    border-radius: 10px;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
}
.draggable-item img {
    width: calc(100% - 16px);
    height: calc(100% - 16px);
    object-fit: cover;
}
.actions {
    width: calc(100% - 16px);
    height: calc(100% - 16px);
    background: rgba(0, 0, 0, 0.5);
    position: absolute;
    display: flex;
    gap: 10px;
    justify-content: center;
    align-items: center;
    opacity: 0;
    &:hover {
        opacity: 1;
    }
}
.preview-icon,
.delete-icon {
    font-size: 20px;
    color: rgba(255, 255, 255, 0.5);
    cursor: pointer;
}
.preview-icon:hover {
    color: #fff;
}
.delete-icon:hover {
    color: #fff;
}
</style>
